#include "server.h"
#include "mysqlManager.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>
#include <thread>
#include <vector>
#include <mysql/mysql.h>
#include <unordered_map>
#include <pthread.h>
#include <set>
#include <fstream>
#include<sys/epoll.h>
#include<errno.h>

using namespace std;

namespace tally
{


TallyServer::TallyServer(std::string addr, short port) : m_addr(addr), m_port(port), m_socket(-1), m_maxEvents(10000)
{
    cout << "---------欢迎使用云记账服务器---------" << endl;

    int LISTENQ = 200;
    int i, maxi, connfd, sockfd, epfd, nfds;  
    ssize_t n;  
    socklen_t clilen;  
    //声明 epoll_event 结构体的变量, ev 用于注册事件,数组用于回传要处理的事件  
    struct epoll_event ev, events[m_maxEvents];  

    //生成用于处理accept的epoll专用的文件描述符  
    epfd = epoll_create(m_maxEvents);

    // 创建 socket
    struct sockaddr_in clientaddr;  
    struct sockaddr_in serveraddr;  
    m_socket = socket(PF_INET, SOCK_STREAM, 0);  

    //把 socket 设置为非阻塞方式   
    setNonBlocking(m_socket);

    // 设置与要处理的事件相关的文件描述符  
    ev.data.fd = m_socket;  
    // 设置要处理的事件类型  
    ev.events = EPOLLIN | EPOLLET;  
    // 注册 epoll 事件  
    epoll_ctl(epfd, EPOLL_CTL_ADD, m_socket, &ev);

    // 绑定 socket
    bzero(&serveraddr, sizeof(serveraddr));  
    serveraddr.sin_family = AF_INET;  
    serveraddr.sin_addr.s_addr = inet_addr(m_addr.c_str());//此处设为服务器的ip
    serveraddr.sin_port = htons(m_port);  
    bind(m_socket, (sockaddr *)&serveraddr, sizeof(serveraddr));

    // 监听 socket
    listen(m_socket, LISTENQ); 
    clilen = sizeof(clientaddr);
    maxi = 0;


    while(1) {
        // 监听 epoll 事件的发生  
        nfds = epoll_wait(epfd, events, m_maxEvents, 3000);//最后一个参数是 timeout，0:立即返回，-1:一直阻塞直到有事件，x: 等待 x 毫秒
        if(nfds < 0) {
            perror("error: epoll_wait");
            break;
        }
        else if(nfds == 0) {
            cout << "epoll_wait 等待超时" << endl;
            continue;
        }
        //处理所发生的所有事件  
        for(i=0; i < nfds; ++i)  
        {  
            //有新客户端连接服务器
            if(events[i].data.fd == m_socket) 
            {  
                connfd = accept(m_socket,(sockaddr *)&clientaddr, &clilen);  
                if(connfd < 0) {  
                     perror("accept error");
                     exit(1);  
                }  
                else {
                    cout << "IP 地址：" << inet_ntoa(clientaddr.sin_addr) << ", 端口号：" << clientaddr.sin_port << " 连接成功\n";
                }
                // 设置用于读操作的文件描述符  
                ev.data.fd = connfd;  
                // 采用 ET 边缘触发，如果考虑防止多个线程处理同一 socket 需要使用使用 EPOLLONESHOT  
                ev.events = EPOLLIN | EPOLLET;
                //边缘触发要将套接字设为非阻塞
                setNonBlocking(connfd);

                // 注册 ev  
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);  
            }  
            //接收到读事件
            else if(events[i].events & EPOLLIN)  
            {  
                sockfd = events[i].data.fd;
                events[i].data.fd = -1;

                thread t(&TallyServer::messageHandle, this, epfd, connfd);
                t.detach();
            }
        }
    }  
    close(m_socket);
}

TallyServer::~TallyServer()
{
    close(m_socket);
}

void TallyServer::setNonBlocking(int socket)
{  
    int opts;  
    opts = fcntl(socket, F_GETFL);  
    if(opts < 0) {  
        perror("fcntl(sock, GETFL)");  
        exit(1);  
    }
    opts = opts | O_NONBLOCK;  
    if(fcntl(socket,F_SETFL, opts) < 0) {  
        perror("fcntl(sock, SETFL, opts)");  
        exit(1);
    }
}

void TallyServer::messageHandle(int epollFd, int clientFd)
{
    int dataLen = 0;
    const int bufLen = 1024;
    char *buffer = new char[bufLen];
    memset(buffer, 0, bufLen);
    while(1)
    {
        char buf[bufLen] = {0};
        int ret  = recv(clientFd, buf, bufLen, 0);
        if(ret < 0) {
            // 对于非阻塞 IO，下面的事件成立标识数据已经全部读取完毕
            if((errno == EAGAIN) || (errno == EWOULDBLOCK)){
                break;
            }
            cout << "errno:" << errno << endl;
            return;
        }
        else if(ret == 0){
            cout << "recv 返回值为 0" << endl;
            return;
        }
        else{
            // 接收数据
            if(ret + dataLen < bufLen) {
                memcpy(buffer, buf, ret);
                dataLen += ret;
            }
            else {
                char *temp = new char[dataLen + bufLen];
                memcpy(temp, buffer, dataLen);
                memcpy(temp + dataLen, buf, ret);
                dataLen += ret;

                delete[] buffer;
                buffer = nullptr;

                buffer = temp;
            }
        }
    }

    switch (dataLen)
    {
    case sizeof(ReginstInfo):
        userRegist(clientFd, buffer, dataLen);
        break;
    case sizeof(LoginInfo):
        userLogin(clientFd, buffer, dataLen);
        break;
    case sizeof(TallyInfo):
        tallyRecord(clientFd, buffer, dataLen);
        break;
    
    default:
        cout << "错误消息......" << endl;
        break;
    }
    delete[] buffer;
    buffer = nullptr;
}

void TallyServer::userRegist(int clientFd, const char* const d, int len)
{
    ReginstInfo info;
    memcpy(&info, d, len);
    cout << "name: " << info.name << " passwd: " << info.passwd << endl;
    //数据库操作对象生成
    SqlManager sql_manager;
    // 1. 从数据库中读取查询该用户是否存在
    bool query_ret=sql_manager.SqlQueryUser(info.name);
    ReturnResult ret;
    // 2. 如果查询已存在，发送信息给客户端，注册失败，用户已存在
    if(query_ret==true){//已经存在
        cout<<"用户已存在，注册失败！"<<endl;
        //ret = {RequestType::RT_REGIST, false, "该名称用户已经存在，注册失败!"};
        ret.type=RequestType::RT_REGIST;
        ret.result=false;
        strcpy(ret.info,"该名称用户已经存在，注册失败!");
        send(clientFd, &ret, sizeof(ret), 0);
        return;
    } 
    // 3. 如果查询不存在，把用户信息插入到数据库并告诉客户端注册成功
    bool insert_ret = sql_manager.SqlUserRegist(info);
    if(insert_ret==false){
        cout<<"注册失败！"<<endl;
        //ret = {RequestType::RT_REGIST, false, "注册失败"};
        ret.type=RequestType::RT_REGIST;
        ret.result=false;
        strcpy(ret.info,"注册失败");

    }else{
        cout<<"注册成功！"<<endl;
        // ret = {RequestType::RT_REGIST, true, "注册成功"};
        ret.type=RequestType::RT_REGIST;
        ret.result=false;
        strcpy(ret.info,"注册失败");
    }
    
    send(clientFd, &ret, sizeof(ret), 0);
    return;
}


void TallyServer::userLogin(int clientFd, const char* const d, int len)
{   
    LoginInfo info;
    ReturnResult ret;
    SqlManager sql_manager;
    memcpy(&info, d, len);
    cout << "name: " << info.name << " passwd: " << info.passwd << endl;
    //用户名校验
    bool query_ret=sql_manager.SqlQueryUser(info.name);
    if(query_ret==false){
        //ReturnResult ret = {RequestType::RT_LOGIN, false, "用户名不存在,登录失败!"};
        ret.type=RequestType::RT_LOGIN;
        ret.result=false;
        strcpy(ret.info,"用户名不存在,登录失败!");
        send(clientFd, &ret, sizeof(ret), 0);
        return;
    }    
    //密码校验
    if(sql_manager.passwd_cmp(info)==true){
        //ReturnResult ret = {RequestType::RT_LOGIN, true, "登录成功"};
        ret.type=RequestType::RT_LOGIN;
        ret.result=true;
        strcpy(ret.info,"登陆成功!");
        send(clientFd, &ret, sizeof(ret), 0);
    return;
    }else{
        //ReturnResult ret = {RequestType::RT_REGIST, true, "密码错误,登录失败!"};
        ret.type=RequestType::RT_LOGIN;
        ret.result=false;
        strcpy(ret.info,"密码错误,登录失败!");
        send(clientFd, &ret, sizeof(ret), 0);
    }
   
}
//记账功能
void TallyServer::tallyRecord(int clientFd, const char* const d, int len)
{   
    //接收部分
    TallyInfo info;
    SqlManager sql_manager;
    ReturnResult ret;
    memcpy(&info, d, len);
    cout << "类型: " << int(info.type) << " money: " << info.money << endl;
    //数据库记账部分
    bool record_ret = sql_manager.SqlTallyRecord(info);
    if(record_ret==false){
        ret.type=RequestType::RT_RECORD;
        ret.result=false;
        strcpy(ret.info,"记录失败！");
    }else{
        ret.type=RequestType::RT_RECORD;
        ret.result=true;
        strcpy(ret.info,"记录成功！");
    }
    send(clientFd, &ret, sizeof(ret), 0);
    return;
}


} // end of namespace  